package com.gitee.wsl.mathematics.vector.ext

import com.gitee.wsl.ext.number.approx
import com.gitee.wsl.math.random.nextFloat
import com.gitee.wsl.mathematics.base.Mapping2
import com.gitee.wsl.mathematics.base.Mapping3
import com.gitee.wsl.mathematics.base.Mapping4
import com.gitee.wsl.mathematics.vector.vec2.MutableVec2
import com.gitee.wsl.mathematics.vector.vec2.MutableVec2f
import com.gitee.wsl.mathematics.vector.vec2.Vec2
import com.gitee.wsl.mathematics.vector.vec2.Vec2f
import com.gitee.wsl.mathematics.vector.vec3.MutableVec3
import com.gitee.wsl.mathematics.vector.vec3.MutableVec3f
import com.gitee.wsl.mathematics.vector.vec3.Vec3
import com.gitee.wsl.mathematics.vector.vec3.Vec3f
import com.gitee.wsl.mathematics.vector.vec4.MutableVec4
import com.gitee.wsl.mathematics.vector.vec4.MutableVec4f
import com.gitee.wsl.mathematics.vector.vec4.Vec4
import com.gitee.wsl.mathematics.vector.vec4.Vec4f
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.random.Random

private typealias Radian = Float

private const val FLOAT_2_PI = 2 * PI.toFloat()

/** Generate a random [Radian] value between included 0 and excluded 2 * [PI]. */
private fun Random.nextRadian(): Radian = nextFloat() * FLOAT_2_PI

/** Generate a random [Float] value between included [min] and excluded [min] + [length]. */
private fun Random.nextFloat(min: Float, length: Float) = nextFloat() * length + min

/** Generate a random 2-dimensional vector of the given [length] and reduce by [reducer]. */
private inline fun <T> Random.nextVec2(length: Float, reducer: Mapping2<Float,T>): T =
    if (length approx 0f) {
        reducer(0f, 0f)
    } else {
        nextRadian().let { radian -> reducer(sin(radian) * length, cos(radian) * length) }
    }

/** Create a new random [Vec2] of the given [length]. */
fun Random.nextVec2(length: Float = 1f): Vec2 = nextVec2(length, ::Vec2)

/** Set a random vector of the given [length] to [out]. */
fun Random.nextVec2To(length: Float = 1f, out: MutableVec2) = nextVec2(length, out::set)

/** Generate a random 3-dimensional vector of the given [length] and reduce by [reducer]. */
private inline fun <T> Random.nextVec3(length: Float, reducer: Mapping3<Float,T>): T =
    if (length approx 0f) {
        reducer(0f, 0f, 0f)
    } else {
        val z = nextFloat(min = -1f, length = 2f)
        val zLength = sqrt(1f - z * z) * length
        nextRadian().let { radian -> reducer(sin(radian) * zLength, cos(radian) * zLength, z * length) }
    }

/** Create a new random [Vec3] of the given [length]. */
fun Random.nextVec3(length: Float = 1f): Vec3 = nextVec3(length, ::Vec3)

/** Set a random vector of the given [length] to [out]. */
fun Random.nextVec3To(length: Float = 1f, out: MutableVec3) = nextVec3(length, out::set)

/** Generate a random 4-dimensional vector of the given [length] and reduce by [reducer]. */
private inline fun <T> Random.nextVec4(length: Float, reducer: Mapping4<Float,T>): T =
    if (length approx  0f) {
        reducer(0f, 0f, 0f, 0f)
    } else {
        var kx: Float
        var ky: Float
        var k1: Float
        do {
            kx = nextFloat(min = -1f, length = 2f)
            ky = nextFloat(min = -1f, length = 2f)
            k1 = kx * kx + ky * ky
        } while (k1 >= 1f)

        var kz: Float
        var kw: Float
        var k2: Float
        do {
            kz = nextFloat(min = -1f, length = 2f)
            kw = nextFloat(min = -1f, length = 2f)
            k2 = kz * kz + kw * kw
        } while (k2 >= 1f)
        val d = sqrt((1f - k1) / k2)
        reducer(kx * length, ky * length, kz * d * length, kw * d * length)
    }

/** Create a new random [Vec4] of the given [length]. */
fun Random.nextVec4(length: Float = 1f): Vec4 = nextVec4(length, ::Vec4)

/** Set a random vector of the given [length] to [out]. */
fun Random.nextVec4To(length: Float = 1f, out: MutableVec4) = nextVec4(length, out::set)

fun MutableVec2.randomizeSelf(random: Random = Random.Default, length: Float = 1f) = random.nextVec2To(length, this)
fun MutableVec3.randomizeSelf(random: Random = Random.Default, length: Float = 1f) = random.nextVec3To(length, this)
fun MutableVec4.randomizeSelf(random: Random = Random.Default, length: Float = 1f) = random.nextVec4To(length, this)


/** Generate a random [Float] value between included [min] and excluded [min] + [length]. */
//private fun Random.nextFloat(min: Float, length: Float) = nextFloat() * length + min

/** Generate a random 2-dimensional vector of the given [length] and reduce by [reducer]. */
private inline fun <T> Random.nextVec2f(length: Float, reducer: Mapping2<Float,T>): T =
    if (length approx 0f) {
        reducer(0f, 0f)
    } else {
        nextRadian().let { radian -> reducer(sin(radian) * length, cos(radian) * length) }
    }

/** Create a new random [Vec2] of the given [length]. */
fun Random.nextVec2f(length: Float = 1f): Vec2f = nextVec2f(length,::Vec2f)

/** Set a random vector of the given [length] to [out]. */
fun Random.nextVec2fTo(length: Float = 1f, out: MutableVec2f) = nextVec2f(length, out::set)

/** Generate a random 3-dimensional vector of the given [length] and reduce by [reducer]. */
private inline fun <T> Random.nextVec3f(length: Float, reducer: Mapping3<Float,T>): T =
    if (length approx 0f) {
        reducer(0f, 0f, 0f)
    } else {
        val z = nextFloat(min = -1f, length = 2f)
        val zLength = sqrt(1f - z * z) * length
        nextRadian().let { radian -> reducer(sin(radian) * zLength, cos(radian) * zLength, z * length) }
    }

/** Create a new random [Vec3] of the given [length]. */
fun Random.nextVec3f(length: Float = 1f): Vec3f = nextVec3f(length,::Vec3f)

/** Set a random vector of the given [length] to [out]. */
fun Random.nextVec3fTo(length: Float = 1f, out: MutableVec3f) = nextVec3f(length, out::set)

/** Generate a random 4-dimensional vector of the given [length] and reduce by [reducer]. */
private inline fun <T> Random.nextVec4f(length: Float, reducer: Mapping4<Float,T>): T =
    if (length approx  0f) {
        reducer(0f, 0f, 0f, 0f)
    } else {
        var kx: Float
        var ky: Float
        var k1: Float
        do {
            kx = nextFloat(min = -1f, length = 2f)
            ky = nextFloat(min = -1f, length = 2f)
            k1 = kx * kx + ky * ky
        } while (k1 >= 1f)

        var kz: Float
        var kw: Float
        var k2: Float
        do {
            kz = nextFloat(min = -1f, length = 2f)
            kw = nextFloat(min = -1f, length = 2f)
            k2 = kz * kz + kw * kw
        } while (k2 >= 1f)
        val d = sqrt((1f - k1) / k2)
        reducer(kx * length, ky * length, kz * d * length, kw * d * length)
    }

/** Create a new random [Vec4] of the given [length]. */
fun Random.nextVec4f(length: Float = 1f): Vec4f = nextVec4f(length,::Vec4f
)

/** Set a random vector of the given [length] to [out]. */
fun Random.nextVec4fTo(length: Float = 1f, out: MutableVec4f) = nextVec4f(length, out::set)

fun MutableVec2f.randomizeSelf(random: Random = Random.Default, length: Float = 1f) = random.nextVec2fTo(length, this)
fun MutableVec3f.randomizeSelf(random: Random = Random.Default, length: Float = 1f) = random.nextVec3fTo(length, this)
fun MutableVec4f.randomizeSelf(random: Random = Random.Default, length: Float = 1f) = random.nextVec4fTo(length, this)


fun <T> Random.randomInUnitCube(reducer: Mapping3<Float,T>): T {
    return reducer(nextFloat(-1f, 1f),nextFloat(-1f, 1f),nextFloat(-1f, 1f))
}

fun <T> Random.randomInUnitSphere(result:MutableVec3f= MutableVec3f()):Vec3f{
    var guard = 0
    do {
        result.x = nextFloat(-1f, 1f)
        result.y = nextFloat(-1f, 1f)
        result.z = nextFloat(-1f, 1f)
    } while (result.squaredLength > 1f && guard++ < 100)
    return result
}
